<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="icon" href="https://jamesg.blog/favicon.ico" type="image/x-icon">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>KGL Interpreter</title>
    <meta name="description" content="An interpreter for the Knowledge Graph Language">
    <script src="https://kit.fontawesome.com/52ae67a839.js" crossorigin="anonymous" async=""></script>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js"></script>
    <script src="https://unpkg.com/d3-graphviz@3.0.5/build/d3-graphviz.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            line-height: 1.6;
        }
        html {
            margin: 0;
            padding: 0;
            max-height: 100%;
        }
        main {
            padding: 25px;
            height: 100vh;
        }
        input {
            width: 100%;
            height: 100%;
            padding: 10px;
            font-size: 16px;
            border: 1px solid #ccc;
            border-radius: 5px;
            margin-bottom: 10px;
        }
        summary {
            margin-bottom: 5px;
        }
        details > p {
            margin-left: 25px;
        }
        details {
            margin-bottom: 10px;
        }
        ul {
            list-style-type: none;
            margin-bottom: 10px;
        }
        li {
            margin-bottom: 3px;
        }
        #output {
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 20px;
            overflow: auto;
            width: 100%;
            background-color: #f1f1f1;
            margin-right: 10px;
            height: 100%;
        }
        a {
            text-decoration: none;
            color: royalblue;
        }
        #tabs li {
            display: inline;
            margin-right: 10px;
        }
        form, input {
            display: block;
        }
        h1 {
            margin-bottom: 20px;
        }
        .bubble {
            background-color: #e1e1e1;
            border-radius: 5px;
            padding: 10px;
            margin-bottom: 20px;;
            display: inline-block;
        }
        .bubble:hover {
            background-color: darkgray;
        }
        .copy {
            float: right;
            color: darkgreen;
            margin-right: 10px;
        }
        #examples {
            margin-top: 20px;
            list-style-type: none;
        }
        #examples li {
            margin-bottom: 10px;
            margin-left: 10px;
        }
        form {
            display: flex;
            flex-direction: row;
            height: fit-content;
        }
        button {
            padding: 9px;
            border: 1px solid #ccc;
            border-radius: 5px;
            color: green;
            cursor: pointer;
            margin-left: 10px;
            display: flex;
            height: 100%;
        }
        footer {
            margin-top: 10px;
        }
        form i {
            margin-right: 10px;
            margin-top: 15px;
            color: green;
        }
        button i {
            margin-right: 5px;
            margin-top: 5px;
        }
        .fa-code {
            color: green;
        }
        #tabs a:hover {
            background-color: royalblue;
            color: white;
        }
        a:focus, a:hover {
            background-color: yellow;
            color: black;
        }
        #time_taken {
            margin-top: 10px;
            background-color: #f1f1f1;
            padding: 10px;
            border-radius: 5px;
            display: inline-block;
        }
        button:focus {
            background-color: yellow;
            color: black;
        }
        button:hover {
            background-color: green;
            color: black;
            .fa-play {
                color: black;
            }
        }
        #output_container {
            display: flex;
            flex-direction: row;
            max-width: 100%;
            height: 100%;
            max-height: 80vh;
        }
        #graph {
            max-width: 35em;
            min-width: 35em;
            overflow: auto;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        #copied_tooltip {
            display: none;
            position: fixed;
            top: 10px;
            right: 10px;
            background-color: green;
            color: white;
            padding: 10px;
            border-radius: 5px;
            z-index: 1000;
        }
        .copy {
            float: right;
        }
        #output {
            text-align: center;
        }
        #examples {
            font-size: 14px;
            margin-top: 10px;
            text-align: left;
            display: grid;
            grid-template-columns: 1fr 1fr;
        }
        #examples .bubble {
            display: block;
        }
        #examples li {
            background-color: white;
            padding: 10px;
            border-radius: 5px;
            border: 1px solid lightgrey;
            margin-left: 0;
            margin-right: 10px;
            text-align: center;
        }
        #graph {
            text-align: center;
            padding: 10px;
        }
        #history {
            max-width: 100%;
            overflow: auto;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 10px;
            min-width: 200px;
            margin-right: 10px;
        }
        #history li:hover {
            background-color: #f1f1f1;
        }
        #history li:first-child {
            border-top: 1px solid #ccc;
        }
        header {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        #history li {
            cursor: pointer;
            border-bottom: 1px solid #ccc;
            padding: 10px;
        }
        #run {
            min-width: 250px;
        }
        #run_container {
            color: white;
            background-color: green;
        }
        #run_container i {
            color: white;
        }
        @media screen and (max-width: 1000px) {
            #history {
                display: none;
            }
            #examples {
                text-align: center;
            }
            #graph {
                margin-top: 10px;
                max-height: 200px;
            }
            h1 {
                font-size: 24px;
                margin-bottom: 10px;
            }
            #output {
                height: 400px;
                max-height: 400px;
            }
            .copy {
                display: none;
            }
            #graph {
                max-width: 100%;
                min-width: 100%;
                margin-top: 10px;
            }
            #output_container {
                flex-direction: column;
            }
            .bubble {
                display: block;
            }
            .hide-on-mobile {
                display: none;
            }
        }
    </style>
</head>
<body>
    <main>
        <header>
            <h1>KGL Interpreter <i class="fas fa-code"></i></h1>
            <ul id="tabs" class="hide-on-mobile">
                <li><a onclick="setMode('pretty');" href="#" >View Pretty Print</a></li>
                <li><a onclick="setMode('json');" href="#">View Raw JSON</a></li>
                <li><a href="https://github.com/capjamesg/knowledge-graph-language" target="_blank">View Language Reference</a></li>
                <div id="copied_tooltip"><i class="fas fa-check"></i> Copied!</div>
                <li><a class="copy" href="#" onclick="document.getElementById('code').select(); document.execCommand('copy'); document.getElementById('copied_tooltip').style.display = 'block'; setTimeout(() => { document.getElementById('copied_tooltip').style.display = 'none'; }, 3000);"><i class="fas fa-copy"></i> Copy Query to Clipboard</a></li>
            </ul>
        </header>
        <form>
            <input type="text" name="code" id="code" placeholder="{ }" value="{ }" />
            <button onclick="document.getElementById('code').dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' }))" type="button" id="run_container"><span id="run">Run</span> <i class="fas fa-play"></i></button>
        </form>
        <p id="suggestions"></p>
        <div id="output_container">
            <section id="history">
                <h2 style="font-size: 1em;">History</h2>
                <ul id="history_list">
                    <li>Make a query to start a history.</li>
                </ul>
            </section>
            <section id="output">
                <p>Make a query to see the output! 🤗</p>
                <ul id="examples">
                    <li><a href="#" onclick="runExample('{ coffee }')"><span class="bubble">{ coffee }</span></a>  Retrieve connections to a node</li>
                    <li><a href="#" onclick="runExample('{ taylor swift -> created }')"><span class="bubble">{ taylor swift -> created }</span></a>  Retrieve information about a relationship</li>
                    <li><a href="#" onclick="runExample('{ coffee }#')"> <span class="bubble">{ coffee }#</span></a>  Count connections to a node</li>
                </ul>
            </section>
            <div id="graph"><i class="fa-solid fa-network-wired"></i><br><p>A graph of your query results will appear here.</p></div>
        </div>
    </main>
    <script>
        function setHistory() {
            const code = document.getElementById('code').value;
            // history is localstorage
            var history = JSON.parse(localStorage.getItem('history')) || [];

            history.push(code);
            localStorage.setItem('history', JSON.stringify(history));
        }

        var autocomplete_buffer = [];

        function setHistoryVisual () {
            const history = JSON.parse(localStorage.getItem('history')) || [];
            const ul = document.getElementById('history_list');
            // show in reverse order
            history.reverse();
            for (let i = 0; i < history.length; i++) {
                // clear history if i == 0
                if (i === 0) {
                    ul.innerHTML = '';
                }
                // hide > 10
                if (i === 10) {
                    break;
                }
                const li = document.createElement('li');
                li.textContent = history[i];
                li.style.cursor = 'pointer';
                li.onclick = function() {
                    document.getElementById('code').value = history[i];
                    // set styles
                    document.getElementById('output').innerHTML = '';
                    document.getElementById('code').dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' }));
                };
                ul.appendChild(li);
            }
        }

        setHistoryVisual();

        var mode = 'pretty';

        function runExample (example) {
            document.getElementById('code').value = example;
            document.getElementById('code').dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' }));
        }

        function setMode(newMode) {
            mode = newMode;
            document.getElementById('output').innerHTML = '';
            if (newMode === 'json') {
                console.log(last_output);
                const pre = document.createElement('pre');
                pre.textContent = last_output;

                const copy = document.createElement('a');

                copy.textContent = 'Copy';
                copy.href = '#';
                copy.onclick = function() {
                    pre.select();
                    document.execCommand('copy');
                    copy.textContent = 'Copied!';
                    setTimeout(() => {
                        copy.innerHTML = '<i class="fas fa-copy"></i> Copy';
                    }, 3000);
                };
                copy.classList.add('copy');

                document.getElementById('output').appendChild(copy);
                document.getElementById('output').appendChild(pre);
            } else {
                // add "run query again" button
                const button = document.createElement('button');
                button.textContent = 'Run Query Again';
                button.onclick = function() {
                    document.getElementById('code').dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' }));
                };
                document.getElementById('output').appendChild(button);
            }
        }

        function endsWithAny(suffixes, string) {
            return suffixes.some(function (suffix) {
                return string.endsWith(suffix);
            });
        }

        document.getElementById('code').addEventListener('keydown', function(e) {
            if (e.key === 'Tab' && document.activeElement === document.getElementById('code') && !e.shiftKey && !endsWithAny(['}', '!', '?'], document.getElementById('code').value)) {
                e.preventDefault();
                const suggestions = document.getElementById('suggestions').children;
                // if no suggestion, add ->
                if (suggestions.length === 0) {
                    document.getElementById('code').value += '-> ';
                    return;
                }
                // replace all text after last ->
                var code = document.getElementById('code').value;
                const last_arrow = code.lastIndexOf('->');
                var last_part = code.substring(last_arrow + 2);
                code = code.substring(0, last_arrow + 2);
                console.log(last_part);
                document.getElementById('code').value = code + " " + suggestions[0].textContent;
                // break;
            }
        });
        
        document.getElementById('code').addEventListener('input', function(e) {
            var code = document.getElementById('code').value;
            // get text after last ->
            const last_arrow = code.lastIndexOf('->');
            console.log(last_arrow);
            var last_part = code.substring(last_arrow + 2);
            console.log(last_part);

            // if key pressed is space, clear suggestions
            if (e.data === ' ') {
                document.getElementById('suggestions').innerHTML = '';
                return;
            }

            // if there is no { at the start, add it
            if (code[0] !== '{') {
                document.getElementById('code').value = '{ ' + code;
            }

            var code = last_part;
            fetch('/autocomplete', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ query: code })
            })
            .then(response => response.json())
            .then(data => {
                const suggestions = data["completions"];
                const p = document.getElementById('suggestions');
                p.innerHTML = '';
                for (let i = 0; i < suggestions.length; i++) {
                    const span = document.createElement('span');
                    span.textContent = suggestions[i];
                    span.style.marginRight = '10px';
                    span.style.cursor = 'pointer';
                    span.classList.add('bubble');
                    span.onclick = function() {
                        document.getElementById('code').value = "{ " + suggestions[i] + " }";
                        p.innerHTML = '';
                        document.getElementById('code').dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' }));
                    };
                    p.appendChild(span);
                }
            });
        });

        var last_output = '';

        document.getElementById('code').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                e.preventDefault();

                document.getElementById('output').style.textAlign = 'left';
                var code = document.getElementById('code').value;

                fetch('/', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ query: code })
                })
                .then(response => response.json())
                .then(data => {
                    document.getElementById('output').innerHTML = '';
                    if (data["error"]) {
                        const p = document.createElement('p');
                        p.textContent = 'Syntax Error';
                        document.getElementById('output').appendChild(p);
                        return;
                    }

                    var dot = data["dot"];

                    setHistory();

                    // remove #graph innertext
                    document.getElementById('graph').innerHTML = '';

                    d3.select("#graph").graphviz(innerWidth < 600 ? { width: innerWidth, height: 500, zoom: true, fit: true } : { width: 600, height: 500, zoom: true, fit: true }).renderDot(dot);

                    last_output = JSON.stringify(data, null, 2);

                    // if time taken exists
                    if (document.getElementById('time_taken')) {
                        document.getElementById('time_taken').remove();
                    }
                    const time_taken = document.createElement('li');
                    // round to 3 dp
                    data["time_taken"] = Math.round(data["time_taken"] * 1000) / 1000;
                    time_taken.textContent = `Time taken: ${data["time_taken"]}ms`;
                    time_taken.id = 'time_taken';
                    // add as first li in tabs
                    document.getElementById('tabs').firstChild.after(time_taken);
                    
                    if (typeof data["result"][0] === 'number') {
                        const p = document.createElement('p');
                        p.textContent = data["result"][0];
                        document.getElementById('output').appendChild(p);
                        return;
                    }

                    if (typeof data["result"][0] === 'boolean') {
                        const p = document.createElement('p');
                        p.textContent = data["result"][0] ? 'True' : 'False';
                        document.getElementById('output').appendChild(p);
                        return;
                    }

                    if (data["result"].length === 0) {
                        const p = document.createElement('p');
                        p.textContent = 'No results found.';
                        document.getElementById('output').appendChild(p);
                        return;
                    }

                    // add item to autocomplete buffer
                    autocomplete_buffer.push([]);

                    if (mode === 'json') {
                        const pre = document.createElement('pre');
                        pre.textContent = JSON.stringify(data, null, 2);

                        const copy = document.createElement('a');

                        copy.textContent = 'Copy';
                        copy.href = '#';
                        copy.onclick = function() {
                            pre.select();
                            document.execCommand('copy');
                            copy.textContent = 'Copied!';
                            setTimeout(() => {
                                copy.textContent = 'Copy';
                            }, 3000);
                        };
                        copy.classList.add('copy');

                        document.getElementById('output').appendChild(copy);
                        document.getElementById('output').appendChild(pre);
                        last_output = JSON.stringify(data, null, 2);
                        return;
                    }
                    for (let i = 0; i < data["result"].length; i++) {
                        var keys_values = Object.entries(data["result"][i]);

                        if (keys_values[0][0] === "0") {
                            const ul = document.createElement('ul');

                            for (var j = 0; j < keys_values.length; j++) {
                                const li = document.createElement('li');
                                li.textContent = keys_values[j][1];
                                ul.appendChild(li);
                            }
                            document.getElementById('output').appendChild(ul);
                        } else {
                            for (var j = 0; j < keys_values.length; j++) {
                                const details = document.createElement('details');
                                const summary = document.createElement('summary');

                                summary.textContent = keys_values[j][0];
                                summary.id = keys_values[j][0];
                                console.log(summary.id);
                                details.appendChild(summary);
                                
                                // add all summaries to autocomplete buffer
                                autocomplete_buffer[autocomplete_buffer.length - 1].push(keys_values[j][0]);

                                const p = document.createElement('p');

                                if (Object.prototype.toString.call(keys_values[j][1]) === '[object Array]') {
                                    // add p
                                    p.textContent = keys_values[j][1].join(', ');

                                }  else {
                                    // parse as json, which needs nesting w/ details + summary
                                    if (typeof keys_values[j][1] === 'object') {
                                        for (var key in keys_values[j][1]) {
                                            const nested_p = document.createElement('p');
                                            nested_p.textContent = key + ': ' + keys_values[j][1][key];
                                            details.appendChild(nested_p);
                                        }
                                    } else {
                                        p.textContent = keys_values[j][1];
                                    }
                                
                            }
                                
                                details.appendChild(p);
                                details.open = true;
                                document.getElementById('output').appendChild(details);
                                
                            }
                        }
                    }
                    // add to hsitory
                    setHistoryVisual();
                });
            }
        });

        // if q= query string, set in input field
        const urlParams = new URLSearchParams(window.location.search);
        const q = urlParams.get('q');
        if (q) {
            document.getElementById('code').value = q;
            document.getElementById('code').dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' }));
        }
    </script>
</body>
</html>